//
// Authenticate.cpp
// libprotea
// Tools Library for the Protea Project / Functionality for user authentication
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

//#define USE_LDAP
//#define USE_PAM

// xerces
#include <xercesc/dom/DOM.hpp>
#include <xercesc/parsers/XercesDOMParser.hpp>
#include <xercesc/sax/ErrorHandler.hpp>
#include <xercesc/sax/SAXParseException.hpp>

#include "../defines.h"
#include "../xmlhelper.h"
#include "../Appdic/appdic.h"
#include "../Security/base64.h"
#include "../Security/hash.h"
#include "../OSDep/files.h"
#include "../OSDep/utf8.h"
#include "../OSDep/registry.h"
#include "AppInterface.h"
#include "security.h"

#include "../defns.h"
using namespace ProteaApp1;

// Fix broken xerces in debug mode
#undef new

XCAuthenticate::XCAuthenticate() {
	auth_method = AUTHENTICATE_LOCAL;
	auth_parameter = NULL;
};

XCAuthenticate::~XCAuthenticate() {
//	printf( "Authenticate clear\n" );
	AuthClear();
};

void XCAuthenticate::AuthClear() {
	if (auth_parameter)
		delete [] auth_parameter;
	auth_parameter = NULL;
};


void XCAuthenticate::HandleUserList( XERCES_CPP_NAMESPACE::DOMNode* node ) {
	bool result = true;

	node = node->getFirstChild();
	while (node && result) {
		if (node->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE) {
			UniTranslator name( node->getNodeName() );
			if (strcmp(name, "UserDef")==0)
				HandleUserDef( node );
			else {
				printf( "Bad XML format: %s\n", name.UTF8() );
				result = false;
			};
		};
		node = node->getNextSibling();
	};
};

void XCAuthenticate::HandleUserDef( XERCES_CPP_NAMESPACE::DOMNode* node ) {
	char* user_name = NULL;
	char* user_password = NULL;
	char* user_descr = NULL;

	// Get properties for the user...
	XERCES_CPP_NAMESPACE::DOMNamedNodeMap* userinfo = node->getAttributes();
	for (unsigned int i=0; i<userinfo->getLength(); i++) {
		XERCES_CPP_NAMESPACE::DOMNode* anode = userinfo->item(i);
		UniTranslator name( anode->getNodeName() );
		UniTranslator value( anode->getNodeValue() );

		if (strcmp( name, "Name" )==0)
			user_name = strdup( value );
		else if (strcmp( name, "Password" )==0)
			user_password = strdup( value );
		else if (strcmp( name, "Description" )==0)
			user_descr = strdup( value );
		else 
			printf( "BAD XML!\n" );
	};

	// Add the user to the list
	users.Add( user_name, user_password, user_descr );

	if (user_name)
		free( user_name );
	if (user_password)
		free( user_password );
	if (user_descr)
		free( user_descr );
};

void XCAuthenticate::HandleGroupList( XERCES_CPP_NAMESPACE::DOMNode* node ) {
	node = node->getFirstChild();
	while (node) {
		if (node->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE) {
			UniTranslator name( node->getNodeName() );
			if (strcmp(name, "GroupDef")==0)
				HandleGroupDef( node );
			else
				printf( "Bad XML format: %s\n", name.UTF8() );
		};
		node = node->getNextSibling();
	};
};

void XCAuthenticate::HandleGroupDef( XERCES_CPP_NAMESPACE::DOMNode* node ) {
	char* group_name = NULL;
	char* group_descr = NULL;

	// Get properties for the user...
	XERCES_CPP_NAMESPACE::DOMNamedNodeMap* userinfo = node->getAttributes();
	for (unsigned int i=0; i<userinfo->getLength(); i++) {
		XERCES_CPP_NAMESPACE::DOMNode* anode = userinfo->item(i);
		UniTranslator name( anode->getNodeName() );
		UniTranslator value( anode->getNodeValue() );

		if (strcmp( name, "Name" )==0)
			group_name = strdup( value );
		else if (strcmp( name, "Description" )==0)
			group_descr = strdup( value );
		else 
			printf( "BAD XML!\n" );
	};

	// Add the group to the list
	XCGroupInformation* group = groups.Add( group_name, group_descr );

	if (group_name)
		free( group_name );
	if (group_descr)
		free( group_descr );

	// Now add users to the group...
	node = node->getFirstChild();
	while (node) {
		if (node->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE) {
			UniTranslator name( node->getNodeName() );
			if (strcmp(name, "User")==0) {
				// Get properties for the user...
				XERCES_CPP_NAMESPACE::DOMNamedNodeMap* userinfo = node->getAttributes();
				for (unsigned int i=0; i<userinfo->getLength(); i++) {
					XERCES_CPP_NAMESPACE::DOMNode* anode = userinfo->item(i);
					UniTranslator name( anode->getNodeName() );
					UniTranslator value( anode->getNodeValue() );

					if (strcmp( name, "Name" )==0) {
						group->AddUser( users.Find( value ) );
					} else
						printf( "BAD XML!\n" );
				};
			} else {
				printf( "BAD XML\n" );
				return;
			};
		};
		node = node->getNextSibling();
	};
};

void XCAuthenticate::HandleApplication( XERCES_CPP_NAMESPACE::DOMNode* node ) {
	int number = 0;
	bool default_value = false;

	// Get properties for the user...
	XERCES_CPP_NAMESPACE::DOMNamedNodeMap* userinfo = node->getAttributes();
	for (unsigned int i=0; i<userinfo->getLength(); i++) {
		XERCES_CPP_NAMESPACE::DOMNode* anode = userinfo->item(i);
		UniTranslator name( anode->getNodeName() );
		UniTranslator value( anode->getNodeValue() );

		if (strcmp( name, "Default" )==0) {
			if (strcmp( value, "Allow" )==0)
                default_value = true;
			else if (strcmp( value, "Deny" )==0)
				default_value = false;
			else
				printf( "BAD XML\n" );
		} else if (strcmp( name, "Number" )==0)
			number = atoi( value );
		else 
			printf( "BAD XML!\n" );
	};

	// Set the default security
	XCSecurityApplication* app = new XCSecurityApplication( number, default_value );

	// Add our application
	apps.AddApplication( app );

	// Look at children of this application
	node = node->getFirstChild();
	while (node) {
		if (node->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE) {
			UniTranslator name( node->getNodeName() );
			if (strcmp(name, "Transactions")==0)
				HandleSecurityItems( node, "Transaction", &app->trans );
			else if (strcmp(name, "Capabilities")==0)
				HandleSecurityItems( node, "Capability", &app->caps );
			else if (strcmp(name, "DataItems")==0)
				HandleSecurityItems( node, "DataItem", &app->data_items );
			else
				HandleRule( node, app );
		};
		node = node->getNextSibling();
	};
};

void XCAuthenticate::HandleSecurityItems( XERCES_CPP_NAMESPACE::DOMNode* node, char* name, XCSecurityItems* items ) {
	bool default_value = false;

	// Get properties for the user...
	XERCES_CPP_NAMESPACE::DOMNamedNodeMap* userinfo = node->getAttributes();
	for (unsigned int i=0; i<userinfo->getLength(); i++) {
		XERCES_CPP_NAMESPACE::DOMNode* anode = userinfo->item(i);
		UniTranslator name( anode->getNodeName() );
		UniTranslator value( anode->getNodeValue() );

		if (strcmp( name, "Default" )==0) {
			if (strcmp( value, "Allow" )==0)
                default_value = true;
			else if (strcmp( value, "Deny" )==0)
				default_value = false;
			else
				printf( "BAD XML\n" );
		} else 
			printf( "BAD XML!\n" );
	};

	// Set the default security
	items->default_allow = default_value;

	// Look at children of this application
	node = node->getFirstChild();
	while (node) {
		if (node->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE) {
			UniTranslator nname( node->getNodeName() );
			if (strcmp(nname, name)==0)
				HandleSecurityItem( node, items );
			else
				HandleRule( node, items );
		};
		node = node->getNextSibling();
	};
};

void XCAuthenticate::HandleSecurityItem( XERCES_CPP_NAMESPACE::DOMNode* node, XCSecurityItems* items ) {
	int number = 0;
	bool default_value = false;

	// Get properties for the user...
	XERCES_CPP_NAMESPACE::DOMNamedNodeMap* userinfo = node->getAttributes();
	for (unsigned int i=0; i<userinfo->getLength(); i++) {
		XERCES_CPP_NAMESPACE::DOMNode* anode = userinfo->item(i);
		UniTranslator name( anode->getNodeName() );
		UniTranslator value( anode->getNodeValue() );

		if (strcmp( name, "Default" )==0) {
			if (strcmp( value, "Allow" )==0)
                default_value = true;
			else if (strcmp( value, "Deny" )==0)
				default_value = false;
			else
				printf( "BAD XML\n" );
		} else if (strcmp( name, "Number" )==0)
			number = atoi( value );
		else 
			printf( "BAD XML!\n" );
	};

	// Create the transaction record
	XCSecurityItem* mytrans = items->AddItem( number );

	// Set the default security
	mytrans->default_allow = default_value;

	// Look at children of this application
	node = node->getFirstChild();
	while (node) {
		if (node->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE)
			HandleRule( node, mytrans );
		node = node->getNextSibling();
	};
};

void XCAuthenticate::HandleRule( XERCES_CPP_NAMESPACE::DOMNode* node, XCSecurityRuleList* list ) {
	UniTranslator name( node->getNodeName() );
	bool IsUser;
	bool allow = false;
	char* user = NULL;

	// Determine whether we are adding a group or a user
	if (strcmp( name, "Group" )==0)
		IsUser = false;
	else if (strcmp( name, "User" )==0)
		IsUser = true;
	else {
		printf( "BAD XML\n" );
		return;
	};

	// Check attributes
	XERCES_CPP_NAMESPACE::DOMNamedNodeMap* info = node->getAttributes();
	for (unsigned int i=0; i<info->getLength(); i++) {
		XERCES_CPP_NAMESPACE::DOMNode* anode = info->item(i);
		UniTranslator name( anode->getNodeName() );
		UniTranslator value( anode->getNodeValue() );

		if (strcmp( name, "Name" )==0)
			user = strdup( value );
		else if (strcmp( name, "Mode" )==0) {
			if (strcmp( value, "Allow" )==0)
				allow = true;
			else if (strcmp( value, "Deny" )==0)
				allow = false;
			else {
				printf( "BAD XML\n" );
				return;
			};
		} else 
			printf( "BAD XML!\n" );
	};

	// Add the rule
	list->Add( this, user, IsUser, allow );
};

void XCAuthenticate::HandleSecurity( XERCES_CPP_NAMESPACE::DOMNode* node ) {
	node = node->getFirstChild();
	while (node) {
		if (node->getNodeType() == XERCES_CPP_NAMESPACE::DOMNode::ELEMENT_NODE) {
			UniTranslator name( node->getNodeName() );
			if (strcmp(name, "UserList")==0)
				HandleUserList( node );
			else if (strcmp(name, "GroupList")==0)
				HandleGroupList( node );
			else if (strcmp(name, "Application")==0)
				HandleApplication( node );
			else
				printf( "Bad XML format: %s\n", name.UTF8() );
		};
		node = node->getNextSibling();
	};
};


void XCAuthenticate::AuthInit( const char* filename ) {
	char tempfile[ MAX_PATH ];
	// get startup parameters from gcs.xml configuration file
	XCRegistry registry;
	char* root;
	try {
		root = registry.GetStringValue( "XSIROOT" );
	} catch( XCToolsError& error ) {
		// On error, set the root to NULL
		root = NULL;
	};

	if (root) {
		sprintf( tempfile, "%s%sTools%ssecurity.xml", root, OSFILEDELIM, OSFILEDELIM );
		free( root );
	} else
		sprintf( tempfile, "security.xml" );

	// Parse XML
	XERCES_CPP_NAMESPACE::DOMDocument* doc;
	XERCES_CPP_NAMESPACE::XercesDOMParser *parser = new XERCES_CPP_NAMESPACE::XercesDOMParser;
	DOMTreeConsoleErrorReporter *errReporter = new DOMTreeConsoleErrorReporter();
	parser->setErrorHandler( errReporter );
	bool result = true;

	try {
		parser->setIncludeIgnorableWhitespace( false );
		parser->parse( tempfile );
		doc = parser->getDocument();
		assert( doc );

		if (!errReporter->getSawErrors()) {
			XERCES_CPP_NAMESPACE::DOMNode* node = doc->getDocumentElement();
			while (node) {
				UniTranslator name( node->getNodeName() );
				if (strcmp(name, "Security")==0)
					HandleSecurity( node );
				else
					printf( "Bad XML format: %s\n", name.UTF8() );

				node = node->getNextSibling();
			};
		} else {
			cout << "Error reading security tables!" << endl;
			result = false;
		};
	} catch( const XERCES_CPP_NAMESPACE::XMLException& e ) {
		cerr << "An error occurred during parsing\n  Message: " << UniTranslator( e.getMessage() ).UTF8() << endl;
		result = false;
	} catch( const XERCES_CPP_NAMESPACE::DOMException& e ) {
		cerr << "A DOM error occurred during parsing\n  DOMException code: " << e.code << endl;
		result = false;
	};
	delete parser;
};



XCCredentials* XCAuthenticate::Authenticate( char* user, char* password ) {
	INFUNC( XCAuthenticate::Authenticate, NULL );

//	printf( "Authenticate user: %s\n", user );

	// Check to see if user is listed in the security tables
	XCCredentials* creds = new XCCredentials( user, this );
	if (creds->GetUserIndex()) {
		bool result = false;

		// Choose the appropriate authentication method
		switch( auth_method ) {
			case AUTHENTICATE_LOCAL:
				result = AuthenticateLocal( creds, password );
				break;

			case AUTHENTICATE_LDAP:
				result = AuthenticateLDAP( creds, password );
				break;

			case AUTHENTICATE_PAM:
				result = AuthenticatePAM( creds, password );
				break;

			default:
				RAISETOOLSERROR( ERR_AUTHENTICATION_NOT_SUPPORTED );
		};

		if (result)
			return creds;
	};

	// All other cases fall thru to here
	delete creds;
	OUTFUNCRET( NULL );
};

//
// Take the supplied password, and get an MD5 digest from it
//
// Pass in cleartext passwords
// Requires a 25 byte buffer to be passed into the function
//
void XCAuthenticate::EncryptPassword( const char* password, char* result ) {
	XCBase64 base64;
	XCMD5Hash hash;

	*result = 0;

	// Initialize context
	hash.Init();

	// Start creating the digest
	hash.Update( (const unsigned char*)password, strlen( password ) );

	// Base64 encoding of the digest data
	base64.Encode( result, (const unsigned char*)hash.Final(), hash.GetDigestSize() );
};



bool XCAuthenticate::AuthenticateLocal( XCCredentials* creds, char* password ) {
	char result[ 25 ];

	// Do an MD5 hash on the password
	EncryptPassword( password, result );

	return (strcmp( result, creds->GetUserPassword() )==0);
};
